Introducción

En este documento se abarcarán los siguientes temas:

  • Máquinas de Soporte Vectorial - SVM
  • Estandarización con nuevos individuos
  • Hiperplanos de separación y de soporte
  • Gráfico de Distribución de la Variable a Predecir.
  • Gráficos de Poder Predictivo.

Paquetes, Módulos y Funciones

Se hará uso de los siguientes Paquetes, Módulos y Funciones

import pandas as pd
import numpy  as np
import matplotlib.pyplot as plt
from predictPy import Analisis_Predictivo
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
import warnings
warnings.filterwarnings("ignore")

Ejemplo Datos de Iris

Lectura de datos

datos = pd.read_csv('../datos/iris.csv',delimiter = ';',decimal = ".")
print(datos.shape)
(150, 5)
print(datos.head())
   s.largo  s.ancho  p.largo  p.ancho    tipo
0      5.1      3.5      1.4      0.2  setosa
1      4.9      3.0      1.4      0.2  setosa
2      4.7      3.2      1.3      0.2  setosa
3      4.6      3.1      1.5      0.2  setosa
4      5.0      3.6      1.4      0.2  setosa
print(datos.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 150 entries, 0 to 149
Data columns (total 5 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   s.largo  150 non-null    float64
 1   s.ancho  150 non-null    float64
 2   p.largo  150 non-null    float64
 3   p.ancho  150 non-null    float64
 4   tipo     150 non-null    object 
dtypes: float64(4), object(1)
memory usage: 6.0+ KB
None

Distribución de la variable a predecir

analisis_Iris = Analisis_Predictivo(datos, predecir="tipo")
analisis_Iris.distribucion_variable_predecir()
plt.show()

Clase SVC() de scikit-learn

Ejemplo 1

Construcción de modelo

instancia_svm = SVC(kernel="rbf")

analisis_Iris = Analisis_Predictivo(datos,predecir= "tipo",modelo=instancia_svm, train_size= 0.7)

Evaluación del modelo

resultados = analisis_Iris.fit_predict_resultados()

Matriz de Confusión:
[[15  0  0]
 [ 0  9  1]
 [ 0  0 20]]

Precisión Global:
0.9777777777777777

Error Global:
0.022222222222222254

Precisión por categoría:
   setosa  versicolor  virginica
0     1.0         0.9        1.0

Ejemplo 2

Construcción de modelo

# Tenemos disponibles los núcleos linear, poly, rbf, sigmoid, precomputed. Por defecto se utiliza rbf
# Si utilizamos poly podemos también indicar el grado del polinomio, esto mediante el parámetro degree. Por defecto degree = 3

#  El parámetro C indica la fuerza de la regularización.

instancia_svm = SVC(kernel="poly", degree = 3, C = 20)

analisis_Iris = Analisis_Predictivo(datos,predecir= "tipo",modelo=instancia_svm, train_size= 0.7)

Evaluación del modelo

resultados = analisis_Iris.fit_predict_resultados()

Matriz de Confusión:
[[18  0  0]
 [ 0 12  4]
 [ 0  1 10]]

Precisión Global:
0.8888888888888888

Error Global:
0.11111111111111116

Precisión por categoría:
   setosa  versicolor  virginica
0     1.0        0.75   0.909091

Ejemplo Scoring de Crédito

datos = pd.read_csv('../datos/MuestraCredito5000V2.csv', delimiter = ';', decimal = ".", header = 0)
print(datos.shape)
(5000, 6)
print(datos.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5000 entries, 0 to 4999
Data columns (total 6 columns):
 #   Column             Non-Null Count  Dtype 
---  ------             --------------  ----- 
 0   MontoCredito       5000 non-null   int64 
 1   IngresoNeto        5000 non-null   int64 
 2   CoefCreditoAvaluo  5000 non-null   int64 
 3   MontoCuota         5000 non-null   object
 4   GradoAcademico     5000 non-null   object
 5   BuenPagador        5000 non-null   object
dtypes: int64(3), object(3)
memory usage: 234.5+ KB
None

Preparación de datos

# Convierte las variables a categórica
datos['IngresoNeto'] = datos['IngresoNeto'].astype('category')
datos['MontoCuota'] = datos['MontoCuota'].astype('category')
datos['GradoAcademico'] = datos['GradoAcademico'].astype('category')

#Convertimos a Dummy algunas de las variables predictoras
datos = pd.get_dummies(datos, columns=["IngresoNeto", "MontoCuota", "GradoAcademico"])

datos.head()
   MontoCredito  ...  GradoAcademico_Licenciatura
0         14327  ...                            0
1        111404  ...                            0
2         21128  ...                            0
3         15426  ...                            0
4         10351  ...                            0

[5 rows x 11 columns]

Balance de las clases en la variable a predecir - Problema desvalanceado

analisis_Credito = Analisis_Predictivo(datos, predecir="BuenPagador")
analisis_Credito.distribucion_variable_predecir()
plt.show()

Ejemplo 1

Construcción de modelo

instancia_svm = SVC(kernel="linear", C = 20)

analisis_Credito = Analisis_Predictivo(datos,predecir= "BuenPagador",modelo=instancia_svm, train_size= 0.75)

Evaluación del modelo

resultados = analisis_Credito.fit_predict_resultados()

Matriz de Confusión:
[[   0  167]
 [   0 1083]]

Precisión Global:
0.8664

Error Global:
0.13360000000000005

Precisión por categoría:
    No   Si
0  0.0  1.0

Diferentes Núcleos.

RBF

instancia_svm = SVC(kernel="rbf", C = 15)

analisis_Credito = Analisis_Predictivo(datos,predecir= "BuenPagador",modelo=instancia_svm, train_size= 0.75)

resultados = analisis_Credito.fit_predict_resultados()

Matriz de Confusión:
[[ 125   47]
 [  20 1058]]

Precisión Global:
0.9464

Error Global:
0.05359999999999998

Precisión por categoría:
         No        Si
0  0.726744  0.981447

SIGMOID

instancia_svm = SVC(kernel="sigmoid",  C = 15)

analisis_Credito = Analisis_Predictivo(datos,predecir= "BuenPagador",modelo=instancia_svm, train_size= 0.75)

resultados = analisis_Credito.fit_predict_resultados()

Matriz de Confusión:
[[ 23 145]
 [133 949]]

Precisión Global:
0.7776

Error Global:
0.22240000000000004

Precisión por categoría:
         No        Si
0  0.136905  0.877079

POLY

instancia_svm = SVC(kernel="poly", degree = 4,  C = 15)

analisis_Credito = Analisis_Predictivo(datos,predecir= "BuenPagador",modelo=instancia_svm, train_size= 0.75)

resultados = analisis_Credito.fit_predict_resultados()

Matriz de Confusión:
[[ 107   88]
 [  12 1043]]

Precisión Global:
0.92

Error Global:
0.07999999999999996

Precisión por categoría:
         No        Si
0  0.548718  0.988626

Ejemplo SAHeart

datos = pd.read_csv('../datos/SAheart.csv', delimiter = ';', decimal = ".")
print("SAheart shape: ", datos.shape, "\n")
SAheart shape:  (462, 10) 
print(datos.info(), "\n")
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 462 entries, 0 to 461
Data columns (total 10 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   sbp        462 non-null    int64  
 1   tobacco    462 non-null    float64
 2   ldl        462 non-null    float64
 3   adiposity  462 non-null    float64
 4   famhist    462 non-null    object 
 5   typea      462 non-null    int64  
 6   obesity    462 non-null    float64
 7   alcohol    462 non-null    float64
 8   age        462 non-null    int64  
 9   chd        462 non-null    object 
dtypes: float64(5), int64(3), object(2)
memory usage: 36.2+ KB
None 
datos.head()
   sbp  tobacco   ldl  adiposity  famhist  typea  obesity  alcohol  age chd
0  160    12.00  5.73      23.11  Present     49    25.30    97.20   52  Si
1  144     0.01  4.41      28.61   Absent     55    28.87     2.06   63  Si
2  118     0.08  3.48      32.28  Present     52    29.14     3.81   46  No
3  170     7.50  6.41      38.03  Present     51    31.99    24.26   58  Si
4  134    13.60  3.50      27.78  Present     60    25.99    57.34   49  Si

Equilibrio de la variable a predecir

analisis_SAheart = Analisis_Predictivo(datos, predecir="chd")
analisis_SAheart.distribucion_variable_predecir()
plt.show()

Preparación de datos

# Convierte las variables de object a categórica
datos['famhist'] = datos['famhist'].astype('category')

#Convertimos a Dummy algunas de las variables predictoras
datos = pd.get_dummies(datos,columns=["famhist"])

datos.head()
   sbp  tobacco   ldl  adiposity  ...  age  chd  famhist_Absent  famhist_Present
0  160    12.00  5.73      23.11  ...   52   Si               0                1
1  144     0.01  4.41      28.61  ...   63   Si               1                0
2  118     0.08  3.48      32.28  ...   46   No               0                1
3  170     7.50  6.41      38.03  ...   58   Si               0                1
4  134    13.60  3.50      27.78  ...   49   Si               0                1

[5 rows x 11 columns]

Ejemplo 1

Construcción y Evaluación del modelo

instancia_svm = SVC(kernel="rbf",  C = 10)

analisis_SAHeart = Analisis_Predictivo(datos,predecir= "chd",modelo=instancia_svm, train_size= 0.8)

resultados = analisis_SAHeart.fit_predict_resultados()

Matriz de Confusión:
[[51  9]
 [18 15]]

Precisión Global:
0.7096774193548387

Error Global:
0.29032258064516125

Precisión por categoría:
     No        Si
0  0.85  0.454545

Diferentes Núcleos

Construcción y Evaluación del modelo

POLY

instancia_svm = SVC(kernel="poly", degree = 5, C = 10)

analisis_SAHeart = Analisis_Predictivo(datos,predecir= "chd",modelo=instancia_svm, train_size= 0.8)

resultados = analisis_SAHeart.fit_predict_resultados()

Matriz de Confusión:
[[55  4]
 [21 13]]

Precisión Global:
0.7311827956989247

Error Global:
0.26881720430107525

Precisión por categoría:
         No        Si
0  0.932203  0.382353

LINEAR

instancia_svm = SVC(kernel="linear",  C = 10)

analisis_SAHeart = Analisis_Predictivo(datos,predecir= "chd",modelo=instancia_svm, train_size= 0.8)

resultados = analisis_SAHeart.fit_predict_resultados()

Matriz de Confusión:
[[47 11]
 [21 14]]

Precisión Global:
0.6559139784946236

Error Global:
0.34408602150537637

Precisión por categoría:
         No   Si
0  0.810345  0.4

SIGMOID

instancia_svm = SVC(kernel="sigmoid", C = 10)

analisis_SAHeart = Analisis_Predictivo(datos,predecir= "chd",modelo=instancia_svm, train_size= 0.8)

resultados = analisis_SAHeart.fit_predict_resultados()

Matriz de Confusión:
[[48 15]
 [13 17]]

Precisión Global:
0.6989247311827957

Error Global:
0.30107526881720426

Precisión por categoría:
         No        Si
0  0.761905  0.566667

Estandarización con nuevos individuos

datos = pd.DataFrame({"a": [8, 7, 6.5, 5.5, 9, 7], "b": [10, 8, 9, 8.5, 7.8, 9],
                      "c": [7, 6, 7, 5, 4.5, 6.5], "d": [8, 7, 6.7, 7.8, 9, 8]})
datos
     a     b    c    d
0  8.0  10.0  7.0  8.0
1  7.0   8.0  6.0  7.0
2  6.5   9.0  7.0  6.7
3  5.5   8.5  5.0  7.8
4  9.0   7.8  4.5  9.0
5  7.0   9.0  6.5  8.0

Obtenemos medias y desviaciones estándar

#Desviación estándar poblacional
desviaciones = datos.std(ddof= 0)
print("Desviaciones estándar\n", desviaciones, "\n")

#Medias
Desviaciones estándar
 a    1.105542
b    0.731247
c    0.957427
d    0.747774
dtype: float64 
medias = datos.mean()
print("Medias\n", medias, "\n")
Medias
 a    7.166667
b    8.716667
c    6.000000
d    7.750000
dtype: float64 

Estandarizamos manualmente

datos2 = datos.copy()
for i in range(datos.shape[1]):
    datos2.iloc[:,i] = (datos2.iloc[:,i] - medias[i]) / desviaciones[i]

datos2
          a         b         c         d
0  0.753778  1.754993  1.044466  0.334325
1 -0.150756 -0.980061  0.000000 -1.002976
2 -0.603023  0.387466  1.044466 -1.404167
3 -1.507557 -0.296297 -1.044466  0.066865
4  1.658312 -1.253566 -1.566699  1.671627
5 -0.150756  0.387466  0.522233  0.334325

¿Cómo estandarizamos los datos nuevos de forma manual?

Estandarizar datos nuevos es muy similar a la forma en que se realiza manualmente sin utilizar bibliotecas, la única diferencia consiste en utilizar los valores como la media y desviación estándar obtenidos al momento de estandarizar los datos con los que se entrenó el modelo.

datos_nuevos = pd.DataFrame({"a": [9, 8, 6], "b": [9, 7.5, 8], "c": [6.5, 7, 6], "d": [9, 8, 7]})
datos_nuevos
   a    b    c  d
0  9  9.0  6.5  9
1  8  7.5  7.0  8
2  6  8.0  6.0  7
datos_nuevos2 = datos_nuevos.copy()

for i in range(datos.shape[1]):
    datos_nuevos2.iloc[:,i] = (datos_nuevos2.iloc[:,i] - medias[i]) / desviaciones[i]

datos_nuevos2
          a         b         c         d
0  1.658312  0.387466  0.522233  1.671627
1  0.753778 -1.663824  1.044466  0.334325
2 -1.055290 -0.980061  0.000000 -1.002976

Estandarizamos con StandardScaler

datos3 = datos.copy()
estandarizador = StandardScaler()
datos3 = pd.DataFrame(estandarizador.fit_transform(datos3), columns= datos.columns)
datos3
          a         b         c         d
0  0.753778  1.754993  1.044466  0.334325
1 -0.150756 -0.980061  0.000000 -1.002976
2 -0.603023  0.387466  1.044466 -1.404167
3 -1.507557 -0.296297 -1.044466  0.066865
4  1.658312 -1.253566 -1.566699  1.671627
5 -0.150756  0.387466  0.522233  0.334325

¿Cómo estandarizamos los datos nuevos utilizando StandardScaler?

Simplemente debemos utilizar la instancia que creamos para estandarizar los datos con los que se entrenó el modelo. Este objeto guarda por dentro los valores de medias y desviaciones estándar.

datos_nuevos3 = datos_nuevos.copy()

#No debemos utilizar fit
datos_nuevos3 = pd.DataFrame(estandarizador.transform(datos_nuevos3), columns= datos_nuevos.columns)
datos_nuevos3
          a         b         c         d
0  1.658312  0.387466  0.522233  1.671627
1  0.753778 -1.663824  1.044466  0.334325
2 -1.055290 -0.980061  0.000000 -1.002976

Hiperplanos de separación y de soporte

Suponga que se tiene la siguiente tabla de datos:
X Y Z Clase
1 0 1 Rojo
1 0 2 Rojo
1 1 2 Rojo
3 1 4 Rojo
1 1 3 Rojo
3 2 3 Azul
1 2 1 Azul
3 2 1 Azul
1 1 0 Azul
d = {'X': [1, 1, 1, 3, 1, 3, 1, 3, 1], 'Y': [0, 0, 1, 1, 1, 2, 2, 2, 1], 
  'Z': [1, 2, 2, 4, 3, 3, 1, 1, 0], 
  'Clase': ['Rojo', 'Rojo', 'Rojo', 'Rojo', 'Rojo', 'Azul', 'Azul', 'Azul', 'Azul']}
df = pd.DataFrame(data = d)
df
   X  Y  Z Clase
0  1  0  1  Rojo
1  1  0  2  Rojo
2  1  1  2  Rojo
3  3  1  4  Rojo
4  1  1  3  Rojo
5  3  2  3  Azul
6  1  2  1  Azul
7  3  2  1  Azul
8  1  1  0  Azul

Graficamos los puntos

import plotly.graph_objs as go
import plotly.express as px

fig = px.scatter_3d(df, x='X', y='Y', z='Z',
              color='Clase',color_discrete_map = {"Rojo": "red", "Azul": "blue"})
fig.show()
def ecuacion_hiperplano(P, Q, R):
    P = np.array(P)
    Q = np.array(Q)
    R = np.array(R)

    PQ = Q - P
    PR = R - P

    T = np.cross(PQ, PR)
    x = np.array([1, -P[0]]) * T[0]
    y = np.array([1, -P[1]]) * T[1]
    z = np.array([1, -P[2]]) * T[2]

    n = x[1] + y[1] + z[1]
    x = x[0]
    y = y[0]
    z = z[0]

    print(str(x) + 'x' + ('+' if y > 0 else '') + str(y) + 'y' + ('+' if z > 0 else '') + str(z) + 'z' + ' = ' + str(-n))

Vectores de Soporte

Clase Roja

# Los siguientes 3 puntos corresponden a los puntos de soporte, encontrados en el gráfico.
P = [1, 1, 2]
Q = [1, 0, 1]
R = [3, 1, 4]

ecuacion_hiperplano(P,Q,R)
-2x-2y+2z = 0

Dividiendo entre 2 en ambos lados de la igualdad tenemos:

\[-x-y+z = 0\]

Luego tenemos que:

\[z=x+y\]

Clase Azul

# Los siguientes 3 puntos corresponden a los puntos de soporte, encontrados en el gráfico.
P = [1, 2, 1]
Q = [1, 1, 0]
R = [3, 2, 3]

ecuacion_hiperplano(P, Q, R)
-2x-2y+2z = -4

Dividiendo entre 2 en ambos lados de la igualdad tenemos:

\[-x-y+z = -2\]

Igualamos a 0:

\[-x-y+z+2 = 0\]

Luego tenemos que:

\[z=x+y−2\]

Ecuación del hiperplano óptimo de separación

\[-x-y+z+1=0\]

Luego tenemos que:

\[z = x+y-1\]

Graficamos

fig = px.scatter_3d(df, x='X', y='Y', z='Z',
              color='Clase',color_discrete_map = {"Rojo": "red", "Azul": "blue"})

x, y = np.meshgrid(range(5), range(5))
z_rojo = x + y
z_azul = x + y - 2
z_optimo = x + y - 1

fig.add_trace(go.Surface(
    x = x,
    y = y,
    z = z_rojo,
    opacity = .7, showscale = False, 
    colorscale = np.repeat('red', x.size, axis = 0)
)).add_trace(go.Surface(
    x = x,
    y = y,
    z = z_azul,
    opacity = .7, showscale = False, 
    colorscale = np.repeat('blue', x.size, axis = 0)
)).add_trace(go.Surface(
    x = x,
    y = y,
    z = z_optimo,
    opacity = .7, showscale = False, 
    colorscale = np.repeat('green', x.size, axis = 0)
))

Hiperplano de separación pero que no es el hiperplano óptimo de separación

La ecuación \(−𝑥−𝑦+𝑧+0.5=0\) corresponde a un hiperplano de separación pero no el óptimo.

fig = px.scatter_3d(df, x='X', y='Y', z='Z',
              color='Clase',color_discrete_map = {"Rojo": "red", "Azul": "blue"})

x, y = np.meshgrid(range(5), range(5))
z_rojo = x + y
z_azul = x + y - 2

#Hiperplano no optimo
z_optimo = x + y - 0.5

fig.add_trace(go.Surface(
    x = x,
    y = y,
    z = z_rojo,
    opacity = .7, showscale = False, 
    colorscale = np.repeat('red', x.size, axis = 0)
)).add_trace(go.Surface(
    x = x,
    y = y,
    z = z_azul,
    opacity = .7, showscale = False, 
    colorscale = np.repeat('blue', x.size, axis = 0)
)).add_trace(go.Surface(
    x = x,
    y = y,
    z = z_optimo,
    opacity = .7, showscale = False, 
    colorscale = np.repeat('green', x.size, axis = 0)
))

Podemos ver como ahora el hiperplano de separación se encuentra más cerca del vector de soporte para la clase Rojo, ya no está exactamente en el medio, por lo que no maximiza el margen y no es óptimo

Observación adicional de manera que las dos clases ya no sean separables por un hiperplano.

#Agregamos un individuo nuevo
#Agregamos el individuo (x = 1, y = 0, z = 4)
d = {'X': [1, 1, 1, 3, 1, 3, 1, 3, 1, 1], 'Y': [0, 0, 1, 1, 1, 2, 2, 2, 1, 0], 
  'Z': [1, 2, 2, 4, 3, 3, 1, 1, 0, 4], 
  'Clase': ['Rojo', 'Rojo', 'Rojo', 'Rojo', 'Rojo', 'Azul', 'Azul', 'Azul', 'Azul','Azul']}
df = pd.DataFrame(data = d)
df
   X  Y  Z Clase
0  1  0  1  Rojo
1  1  0  2  Rojo
2  1  1  2  Rojo
3  3  1  4  Rojo
4  1  1  3  Rojo
5  3  2  3  Azul
6  1  2  1  Azul
7  3  2  1  Azul
8  1  1  0  Azul
9  1  0  4  Azul
fig = px.scatter_3d(df, x='X', y='Y', z='Z',
              color='Clase',color_discrete_map = {"Rojo": "red", "Azul": "blue"})

x, y = np.meshgrid(range(5), range(5))
z_rojo = x + y
z_azul = x + y - 2
z_optimo = x + y - 1

fig.add_trace(go.Surface(
    x = x,
    y = y,
    z = z_rojo,
    opacity = .7, showscale = False, 
    colorscale = np.repeat('red', x.size, axis = 0)
)).add_trace(go.Surface(
    x = x,
    y = y,
    z = z_azul,
    opacity = .7, showscale = False, 
    colorscale = np.repeat('blue', x.size, axis = 0)
)).add_trace(go.Surface(
    x = x,
    y = y,
    z = z_optimo,
    opacity = .7, showscale = False, 
    colorscale = np.repeat('green', x.size, axis = 0)
))